package edu.sjtu.grid.workflow.facts;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.dom4j.Element;

import edu.sjtu.grid.workflow.Util;
import edu.sjtu.grid.workflow.graph.Node;

public class Job implements Node{
	private Facts facts = null;
	
	private String id = null;
	private String name = null;
	private double runTime = 0;

	private int index = -1;
	private List<Use> useList = null;

	private List<Job> parentJobList = null;
	private List<Job> childJobList = null;
	private Map<Job, Long> parentToDataSize = null;
	private Map<Job, Long> childToDataSize = null;
	
	private Double upwardRank = null;
	private Double relativeLFT = null;
	
	public double getTransTime(Job child, PC from, PC to)
	{
		long data = childToDataSize.get(child);
		double bandwidth = this.facts.getPcs().getBandwidth(from, to);
		return bandwidth == 0? 0 : (double)(data / bandwidth);
	}

	public double getRelativeLFT()
	{
		if(this.relativeLFT == null)
		{
			if(this.childJobList == null || this.childJobList.isEmpty())
				this.relativeLFT = (double)0;
			else
			{
				this.relativeLFT = (double)0;
				for(Job childJob : this.childJobList)
				{
					double tmp = childJob.getRelativeLFT() - childJob.getFastestRunTime();
					this.relativeLFT = Math.min(this.relativeLFT, tmp);
				}
			}
		}
		return this.relativeLFT;
	}
	
	@SuppressWarnings("unchecked")
	public Job(Facts facts, Element eJob, int index)
	{
		this.facts = facts;
		this.index = index;
		this.id = eJob.attributeValue("id");
        this.name = eJob.attributeValue("name");
        String sRunTime = eJob.attributeValue("runtime");
        this.runTime = Double.parseDouble(sRunTime);
		this.useList = new ArrayList<Use>();
		this.parentJobList = new ArrayList<Job>();
		this.childJobList = new ArrayList<Job>();
		this.parentToDataSize = new HashMap<Job, Long>();
		this.childToDataSize = new HashMap<Job, Long>();
		
        //use
        for(Iterator<Element> j = eJob.elementIterator("uses"); j.hasNext(); ){
        	Element euse = (Element)j.next();
        	String sFile = euse.attributeValue("file");
        	String sLink = euse.attributeValue("link");	
        	String sSize = euse.attributeValue("size");
        	Use use = new Use(sFile, sLink, sSize);
        	this.useList.add(use);
        }
	}
	
	public String getChildParentString()
	{
		StringBuilder s = new StringBuilder();
		
		for(Job parent : this.parentJobList)
		{
			s.append(this.index + "\t\t");
			s.append(Util.formatOutputString(this.id, 2));
			s.append(parent.index + "\t\t");
			s.append(Util.formatOutputString(parent.id, 2));
			s.append(this.parentToDataSize.get(parent));
			s.append("\n");
		}
		
		return s.toString();
	}
	
	public int getChildNumber()
	{
		return this.childJobList == null ? 0 : this.childJobList.size();
	}
	
	public int getParentNumber()
	{
		return this.parentJobList == null? 0 : this.parentJobList.size();
	}
	
	public String getParentChildString()
	{
		StringBuilder s = new StringBuilder();
		
		for(Job child : this.childJobList)
		{
			s.append(this.index + "\t\t");
			s.append(Util.formatOutputString(this.id, 2));
			s.append(child.index + "\t\t");
			s.append(Util.formatOutputString(child.id, 2));
			s.append(this.childToDataSize.get(child));
			s.append("\n");
		}
		
		return s.toString();
	}
	
	public double getAvgTransTime(Job child)
	{
		double avgBandwidth = this.facts.getPcs().getAvgBandwidth();
		long data = this.childToDataSize.get(child);
		double avgTransTime = data / avgBandwidth;
		return avgTransTime;
	}
	private double getMaxTransTime()
	{
		double max = 0;
		if(this.childJobList != null && !this.childJobList.isEmpty())
		{
			for(Job child : this.childJobList)
			{
				double childUpwardRank = child.getUpwardRank();
				double avgTransTime = this.getAvgTransTime(child);
				double tmp =  childUpwardRank + avgTransTime;
				if(Util.isEqualOrSmall(max, tmp))
					max = tmp;
			}
		}
		return max;
	}
	public double getAvgRunTime()
	{
		double avgSpeed = facts.getPcs().getAvgSpeed();
		double avgRunTime = this.runTime / avgSpeed;
		return avgRunTime;
	}
	public double getFastestRunTime()
	{
		double fastestSpeed = facts.getPcs().getFastestSpeed();
		double fastestRunTime = this.runTime / fastestSpeed;
		return fastestRunTime;
	}
	@Override
	public double getUpwardRank()
	{
		if(this.upwardRank == null)
		{
			double maxTransTime = this.getMaxTransTime();
			double avgRunTime = this.getAvgRunTime();
			this.upwardRank = maxTransTime + avgRunTime + (double)0.01;
		}
		return this.upwardRank;
	}
	
	
	
	public void addChild(Job child)
	{
		long data = 0;
		this.childJobList.add(child);
		for(Use parentuse : this.useList)
			for(Use childuse : child.getuseList())
				if(parentuse.matches(childuse))
					data += (childuse.getSize() + parentuse.getSize()) / 2;
		this.childToDataSize.put(child, data);
	}
	
	public void addParent(Job parent)
	{
		long data = 0;
		this.parentJobList.add(parent);
		for(Use childuse : this.useList)
			for(Use parentuse : parent.getuseList())
				if(parentuse.matches(childuse))
					data += (childuse.getSize() + parentuse.getSize()) / 2;
		this.parentToDataSize.put(parent, data);
	}
	
	public String getId()
	{
		return this.id;
	}
	
	public String getName() {
		return name;
	}

	public double getRunTime() {
		return runTime;
	}

	public List<Use> getuseList() {
		return useList;
	}
	
	public int getIndex() {
		return this.index;
	}
	
	public Facts getFacts() {
		return facts;
	}

	public List<Use> getUseList() {
		return useList;
	}

	public List<Job> getParentJobList() {
		return parentJobList;
	}

	public List<Job> getChildJobList() {
		return childJobList;
	}

	public Map<Job, Long> getParentToDataSize() {
		return parentToDataSize;
	}

	public Map<Job, Long> getChildToDataSize() {
		return childToDataSize;
	}

	public String toString()
	{
		StringBuffer s = new StringBuffer();
		
		s.append(Util.formatOutputString(""+index, 1));
		s.append(Util.formatOutputString(id, 2));
		s.append(Util.formatOutputString(name, 4));
		s.append(Util.formatOutputString(""+runTime, 1));
		s.append("\n");
		
		return s.toString();
	}
	
	public String getUseString()
	{
		StringBuffer s = new StringBuffer();
		
		for(Use use : this.useList)
			s.append(this.index + "\t" + Util.formatOutputString(id, 2)+ use.toString() + "\n");
		
		return s.toString();
		
	}
}
